package udpInvoker.main;

import udpInvoker.util.UdpUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Date;
import java.util.Iterator;
import java.io.IOException;

/**
 * Singleton class. Handles service discovery response-messages on behalf of all Invokables running in the same JVM. Reduces network traffic.
 * Copyright 2008  Espen Skjervold, FFI
 */
public class ServiceDiscoveryBroker implements Runnable{

    private static ServiceDiscoveryBroker instance;

    private HashMap<Long, HashMap> serviceRequestHash;
    private HashMap<Long, Long> timeStampHash;

    private final int responseDelay=2000; //the number of milliseconds to wait after the last remote method has notified, before sending the service response.


    public static ServiceDiscoveryBroker getInstance() {

        if (instance==null)
            instance=new ServiceDiscoveryBroker();

        return instance;

    }


    private ServiceDiscoveryBroker() {
        serviceRequestHash = new  HashMap<Long, HashMap>();
        timeStampHash = new HashMap<Long, Long>();

        Thread serviceDiscoveryResponseSenderThread = new Thread(this);
        serviceDiscoveryResponseSenderThread.start();

    }

    public synchronized void addServiceDiscoveryResponse(long serialNumber, ArrayList<ServiceDescription> serviceDescriptionList) {

        if (!serviceRequestHash.containsKey(serialNumber))
            serviceRequestHash.put(serialNumber, new HashMap<String, ServiceDescription>());

        HashMap<String, ServiceDescription> serviceDescriptionHash = serviceRequestHash.get(serialNumber);

        for (int i=0;i<serviceDescriptionList.size();i++) {
            ServiceDescription serviceDescription = (ServiceDescription) serviceDescriptionList.get(i);
            if (!serviceDescriptionHash.containsKey(serviceDescription.toString())) {
                serviceDescriptionHash.put(serviceDescription.toString(), serviceDescription);
                timeStampHash.put(serialNumber, new Date().getTime());
            }

        }
    }

    public void run() {
        while (true) {
            sleep(1000);

            sendServiceDiscoveryResponses();
        }

    }

    private synchronized void sendServiceDiscoveryResponses() {
        Iterator iterator = timeStampHash.keySet().iterator();

        while (iterator.hasNext()) {
            long serialNumber = (Long) iterator.next();
            long timeStamp = timeStampHash.get(serialNumber);

            if (new Date().getTime() >= timeStamp + responseDelay) {
                sendServiceDiscoveryResponse(serialNumber);
            }
        }

    }

    private synchronized void sendServiceDiscoveryResponse(long serialNumber) {

        ArrayList<ServiceDescription> serviceDescriptionList = new ArrayList();

        HashMap serviceDescriptionHash = serviceRequestHash.get(serialNumber);
        Iterator iterator = serviceDescriptionHash.values().iterator();
        while (iterator.hasNext()) {
            ServiceDescription serviceDescription = (ServiceDescription) iterator.next();
            serviceDescriptionList.add(serviceDescription);
        }


        timeStampHash.remove(serialNumber);
        serviceDescriptionHash.remove(serialNumber);

        InvocationMessage serviceResponse =new InvocationMessage();
        serviceResponse.setType(InvocationMessage.SERVICE_RESPONSE);
        serviceResponse.setResponse(serviceDescriptionList);
        serviceResponse.setSerialNumber(serialNumber);

        try {
            
            UdpUtils.sendMessage(serviceResponse, false, null, 0, null, 0); //todo: enable encryption
            System.out.println("service response-message sent");

        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }



    }

    private void sleep(long millis) {

        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }
}
